﻿namespace StockSharp.Algo.Indicators.Trend
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using StockSharp.Algo.Candles;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// ЗигЗаг.
	/// </summary>
	/// <remarks>
	/// ZigZag отслеживает и соединяет между собой крайние точки графика отстоящие
	/// друг от друга не менее чем на заданный процент по шкале цены.
	/// </remarks>
	public class ZigZag : BaseIndicator<decimal>
	{
		private readonly IList<Candle> _buffer = new List<Candle>();
		private readonly List<decimal> _lowBuffer = new List<decimal>();
		private readonly List<decimal> _highBuffer = new List<decimal>();
		private readonly List<decimal> _zigZagBuffer = new List<decimal>();

		///<summary>
		/// Создать <see cref="ZigZag"/>
		///</summary>
		public ZigZag()
		{
			BackStep = 3;
			Depth = 12;
		}

		///<summary>
		/// Минимальное число баров между локальными максимумами, минимумами.
		///</summary>
		public int BackStep { get; set; }

		///<summary>
		/// Минимум баров, на котором Zigzag не будет строить второй максимум (или минимум),
		/// если тот меньше (или больше) на deviation предыдущего соответственно.
		///</summary>
		public int Depth { get; set; }

		private Unit _deviation = new Unit(0.45m, UnitTypes.Percent);
		///<summary>
		/// Минимальное количество пунктов между максимумами (минимумами) двух соседних баров для того
		/// чтобы индикатор Zigzag сформировал локальную вершину (локальный минимум).
		///</summary>
		public Unit Deviation
		{
			get { return _deviation; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				_deviation = value;
			}
		}

		private Func<Candle, decimal> _highValue = candle => candle.HighPrice;
		///<summary>
		/// Конвертер, который возвращает из свечки цену для поиска максимума.
		///</summary>
		public Func<Candle, decimal> HighValueFunc
		{
			get { return _highValue; }
			set { _highValue = value; }
		}

		private Func<Candle, decimal> _lowValue = candle => candle.LowPrice;
		///<summary>
		/// Конвертер, который возвращает из свечки цену для поиска минимумв.
		///</summary>
		public Func<Candle, decimal> LowValueFunc
		{
			get { return _lowValue; }
			set { _lowValue = value; }
		}

		private Func<Candle, decimal> _currentValue = candle => candle.ClosePrice;
		///<summary>
		/// Конвертер, который возвращает из свечки цену для текущего значения.
		///</summary>
		public Func<Candle, decimal> CurrentValueFunc
		{
			get { return _currentValue; }
			set { _currentValue = value; }
		}

		///<summary>
		/// Текущее значение индикатора
		///</summary>
		public decimal CurrentValue { get; private set; }

		///<summary>
		/// Смещение для последнего значения индикатора
		///</summary>
		public int LastValueShift { get; private set; }

		/// <summary>
		/// Обработать входное значение.
		/// </summary>
		/// <param name="input">Входное значение.</param>
		/// <returns>Результирующее значение.</returns>
		protected override decimal OnProcess(IIndicatorValue input)
		{
			_buffer.Insert(0, input.GetValue<Candle>());
			_lowBuffer.Insert(0, 0);
			_highBuffer.Insert(0, 0);
			_zigZagBuffer.Insert(0, 0);

			const int level = 3;
			int limit;
			decimal lastHigh = 0;
			decimal lastLow = 0;

			if (_buffer.Count - 1 == 0)
			{
				limit = _buffer.Count - Depth;
			}
			else
			{
				int i = 0, count = 0;
				while (count < level && i < _buffer.Count - Depth)
				{
					var res = _zigZagBuffer[i];
					if (res != 0)
					{
						count++;
					}
					i++;
				}
				limit = --i;
			}

			if (_buffer[0].LowPrice == 7966 && _buffer[0].HighPrice == 7976)
			{

			}

			for (int shift = limit; shift >= 0; shift--)
			{
				if(shift == 2)
				{
					
				}

				//--- low
				var val = _buffer.Skip(shift).Take(Depth).Min(v => _lowValue(v));
				if (val == lastLow)
				{
					val = 0.0m;
				}
				else
				{
					lastLow = val;
					if (_lowValue(_buffer[shift]) - val > 0.0m * val / 100)
					{
						val = 0.0m;
					}
					else
					{
						for (var back = 1; back <= BackStep; back++)
						{
							var res = _lowBuffer[shift + back];
							if (res != 0 && res > val)
							{
								_lowBuffer[shift + back] = 0.0m;
							}
						}
					}
				}
				if (_lowValue(_buffer[shift]) == val)
					_lowBuffer[shift] = val;
				else
					_lowBuffer[shift] = 0m;

				//--- high
				val = _buffer.Skip(shift).Take(Depth).Max(v => _highValue(v));
				if (val == lastHigh)
				{
					val = 0.0m;
				}
				else
				{
					lastHigh = val;
					if (val - _highValue(_buffer[shift]) > 0.0m * val / 100)
					{
						val = 0.0m;
					}
					else
					{
						for (var back = 1; back <= BackStep; back++)
						{
							var res = _highBuffer[shift + back];
							if (res != 0 && res < val)
							{
								_highBuffer[shift + back] = 0.0m;
							}
						}
					}
				}
				if (_highValue(_buffer[shift]) == val)
					_highBuffer[shift] = val;
				else
					_highBuffer[shift] = 0m;
			}

			// final cutting 
			lastHigh = -1;
			lastLow = -1;
			int lastHighPos = -1;
			int lastLowPos = -1;

			for (int shift = limit; shift >= 0; shift--)
			{
				decimal curLow = _lowBuffer[shift];
				decimal curHigh = _highBuffer[shift];

				if ((curLow == 0) && (curHigh == 0))
					continue;

				//---
				if (curHigh != 0)
				{
					if (lastHigh > 0)
					{
						if (lastHigh < curHigh)
						{
							_highBuffer[lastHighPos] = 0;
						}
						else
						{
							_highBuffer[shift] = 0;
						}
					}
					//---
					if (lastHigh < curHigh || lastHigh < 0)
					{
						lastHigh = curHigh;
						lastHighPos = shift;
					}
					lastLow = -1;
				}
				//----
				if (curLow != 0)
				{
					if (lastLow > 0)
					{
						if (lastLow > curLow)
						{
							_lowBuffer[lastLowPos] = 0;
						}
						else
						{
							_lowBuffer[shift] = 0;
						}
					}
					//---
					if ((curLow < lastLow) || (lastLow < 0))
					{
						lastLow = curLow;
						lastLowPos = shift;
					}
					lastHigh = -1;
				}
			}

			for (int shift = limit; shift >= 0; shift--)
			{
				if (shift >= _buffer.Count - Depth)
				{
					_zigZagBuffer[shift] = 0.0m;
				}
				else
				{
					var res = _highBuffer[shift];
					if (res != 0.0m)
					{
						_zigZagBuffer[shift] = res;
					}
					else
					{
						_zigZagBuffer[shift] = _lowBuffer[shift];
					}
				}
			}

			int valuesCount = 0, valueId = 0;

			for (; valueId < _zigZagBuffer.Count && valuesCount < 2; valueId++)
			{
				if (_zigZagBuffer[valueId] != 0)
					valuesCount++;
			}

			if (valuesCount == 2)
			{
				IsFormed = true;
				LastValueShift = valueId - 1;

				CurrentValue = _currentValue(_buffer[0]);

				//System.IO.File.AppendAllText(@"d:\1.txt", String.Join("; ", new[] { _zigZagBuffer[LastValueShift].ToString(), CurrentValue.ToString(), Environment.NewLine }));

				return _zigZagBuffer[LastValueShift];
			}

			return this.GetCurrentValue();

			////var values = _zigZagBuffer.TakeWhile(v => (valuesCount <= 2 && v == 0) || ++valuesCount <= 2).ToList();
			//var values = _zigZagBuffer.Where(v => v != 0m).ToList();

			////System.IO.File.AppendAllText(@"d:\1.txt", String.Join("; ", _highBuffer.AsEnumerable().Reverse().Select(b => b.ToString()).ToArray()));
			////System.IO.File.AppendAllText(@"d:\1.txt", Environment.NewLine);
			////System.IO.File.AppendAllText(@"d:\1.txt", String.Join("; ", _lowBuffer.AsEnumerable().Reverse().Select(b => b.ToString()).ToArray()));
			////System.IO.File.AppendAllText(@"d:\1.txt", Environment.NewLine);
			//System.IO.File.AppendAllText(@"d:\1.txt", String.Join("; ", values.Select(b => b.ToString()).ToArray()));
			////System.IO.File.AppendAllText(@"d:\1.txt", String.Join("; ", _zigZagBuffer.Select(b => b.ToString()).ToArray()));
			//System.IO.File.AppendAllText(@"d:\1.txt", Environment.NewLine);

			//if (values.Count > 1)
			//{
			//    //IsFormed = true;
			//    //Values = values;
			//    return values[0];
			//}
			//else
			//    return LastValue;
		}
	}
}